package com.newatlanta.commons.vfs.provider.gae.jar;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.CodeSigner;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileContentInfo;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileType;
import org.apache.commons.vfs.RandomAccessContent;
import org.apache.commons.vfs.util.RandomAccessMode;

import com.newatlanta.commons.vfs.provider.gae.GaeFileObject;

@SuppressWarnings("serial")
public class GaeJarFileObject extends GaeFileObject {
	private static final Log LOG = LogFactory.getLog(GaeJarFileObject.class);

	class Content implements FileContent {

		@Override
		public FileObject getFile() {
			return GaeJarFileObject.this;
		}

		@Override
		public long getSize() throws FileSystemException {
			return doGetContentSize();
		}

		@Override
		public long getLastModifiedTime() throws FileSystemException {
			return doGetLastModifiedTime();
		}

		@Override
		public void setLastModifiedTime(long modTime)
				throws FileSystemException {
			throw new UnsupportedOperationException("setLastModifiedTime");
		}

		@Override
		public boolean hasAttribute(String attrName) throws FileSystemException {
			return false;
		}

		@Override
		public Map<String, Object> getAttributes() throws FileSystemException {
			return Collections.emptyMap();
		}

		@Override
		public String[] getAttributeNames() throws FileSystemException {
			return new String[0];
		}

		@Override
		public Object getAttribute(String attrName) throws FileSystemException {
			return null;
		}

		@Override
		public void setAttribute(String attrName, Object value)
				throws FileSystemException {
			throw new UnsupportedOperationException("setAttribute");
		}

		@Override
		public void removeAttribute(String attrName) throws FileSystemException {
			throw new UnsupportedOperationException("removeAttribute");
			
		}

		@Override
		public Certificate[] getCertificates() throws FileSystemException {
			throw new UnsupportedOperationException("getCertificates");
		}

		@Override
		public InputStream getInputStream() throws FileSystemException {
			return doGetInputStream();
		}

		@Override
		public OutputStream getOutputStream() throws FileSystemException {
			throw new UnsupportedOperationException("getOutputStream");
		}

		@Override
		public RandomAccessContent getRandomAccessContent(RandomAccessMode mode)
				throws FileSystemException {
			throw new UnsupportedOperationException("getRandomAccessContent");
		}

		@Override
		public OutputStream getOutputStream(boolean bAppend)
				throws FileSystemException {
			throw new UnsupportedOperationException("getOutputStream(append)");
		}

		@Override
		public void close() throws FileSystemException {
		}

		@Override
		public FileContentInfo getContentInfo() throws FileSystemException {
			return null;
		}

		@Override
		public boolean isOpen() {
			return true;
		}
		
	}
	
	public class Buffer {
	
		public byte [] getBytes() {
			return buffer;
		}
		public int getLength() {
			return (int)size;
		}
		
	}
	
    /** The JarEntry. */
    protected ZipEntry entry;
    private final Set<String> children = new HashSet<String>();
    
    byte buffer[] ;
    
    private FileType type;
    private long size;
    
    protected GaeJarFileObject(FileName name,
    							ZipEntry entry,
    							GaeJarFileSystem fs,
    							final ZipInputStream jis,
    							boolean zipExists) throws FileSystemException 
    {
        super(name, fs);
        setJarEntry(entry, jis);

              
        if (!zipExists)
        {
            type = FileType.IMAGINARY;
        }
    }

    /**
     * Sets the details for this file object.
     * @throws IOException 
     */
    protected void setJarEntry(final ZipEntry entry, final ZipInputStream jis) throws FileSystemException
    {
    	if( entry == null ) {
    		throw new IllegalArgumentException("entry is null!");
    	}
    	if( jis == null ) {
    		throw new IllegalArgumentException("JarInputStream is null!");
    	}
        if (entry.isDirectory())
        {
            type = FileType.FOLDER;
        }
        else
        {
            type = FileType.FILE;
        }
        
        this.entry = entry;
        
        long sz = entry.getSize();
        int bufferLen = 0;
        
        if( sz > 0 ) {
        	size = sz;
	        buffer = new byte[ (int)sz ];
	        try {
		        int avail ;
		        while( (avail = jis.available()) > 0 && bufferLen < sz ) {
		        	bufferLen += jis.read(buffer, bufferLen, avail);
		        }

				LOG.info( String.format("read entry [%s]  size [%d] red [%d]", entry, sz, bufferLen));
				
			} catch (IOException e) {
				throw new FileSystemException(e);
			}
        }
        else {
	        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(512*1024);
	        byte [] readb = new byte[ 1024 ];
	        try {
		        int avail ;
		        while( (avail = jis.available()) > 0  ) {
		        	jis.read(readb, 0, avail);
		        	baos.write(readb, 0, avail);
		        }	        
		        baos.flush();
		        buffer = baos.toByteArray();
		        size = baos.size()-1;
		        
		        //java.io.FileOutputStream tmp = new java.io.FileOutputStream( String.format("/tmp/%s", entry.getName().replace('/', '_')));
		        //tmp.write(buffer);
		        //tmp.close();
		        
				LOG.info( String.format("read entry [%s]  size [%d] red [%d]", entry, sz, bufferLen));
				
			} catch (IOException e) {
				throw new FileSystemException(e);
			}
	        
        	LOG.warn( String.format("entry [%s] has invalid size [%d]", entry, sz));
        }
       
    }

    /**
     * Attaches a child.
     * @param childName The name of the child.
     */
    public void attachChild(FileName childName)
    {
        children.add(childName.getBaseName());
    }

    /**
     * Determines if this file can be written to.
     *
     * @return <code>true</code> if this file is writeable, <code>false</code> if not.
     * @throws FileSystemException if an error occurs.
     */
    public boolean isWriteable() throws FileSystemException
    {
        return false;
    }

    /**
     * Returns the file's type.
     */
    @Override
    protected FileType doGetType()
    {
        return type;
    }

    /**
     * Lists the children of the file.
     */
    protected String[] doListChildren()
    {
        try
        {
            if (!getType().hasChildren())
            {
                return null;
            }
        }
        catch (FileSystemException e)
        {
            // should not happen as the type has already been cached.
            throw new RuntimeException(e);
        }

        return (String[]) children.toArray(new String[children.size()]);
    }

    /**
     * Returns the size of the file content (in bytes).  Is only called if
     * {@link #doGetType} returns {@link FileType#FILE}.
     */
    @Override
    protected long doGetContentSize()
    {
        return size; //entry.getSize();
    }

    /**
     * Returns the last modified time of this file.
     */
    @Override
    protected long doGetLastModifiedTime()
    {
        return entry.getTime();
    }

    /**
     * Creates an input stream to read the file content from.  Is only called
     * if  {@link #doGetType} returns {@link FileType#FILE}.  The input stream
     * returned by this method is guaranteed to be closed before this
     * method is called again.
     * @throws FileSystemException 
     */
    @Override
    protected InputStream doGetInputStream() throws FileSystemException  {
        return new ByteArrayInputStream(buffer,0,(int)size);
    }

    public final Buffer getBuffer() {
    	return new Buffer();
    }
    
	@Override
	protected FileContent doCreateFileContent() throws FileSystemException {
		return new Content();
	}

	public final CodeSigner[] getCodeSigners() {
		throw new UnsupportedOperationException("getCodeSigners");
	}
}
